﻿using System;
using System.Collections.Generic;
using System.Xml;
using Jayden.Dll.Sc2.Descriptions.Zerg;
using Jayden.Dll.Sc2.Enums;

namespace Jayden.Dll.Sc2.Descriptions
{
	public class EntityDescription : Description
	{
		private string m_IconId;
		private RaceDescription m_Race;
		private Cost m_Cost;
		private int m_Count = 1;
		private bool m_HaveEnergy = false;
		private int? m_LifeTime = null;
		private EntityType m_Type = EntityType.Other;

		private string m_InheritsId;
		private EntityDescription m_Inherits;
		private HashSet<EntityDescription> m_Inheritings;

		private HashSet<string> m_RequirementsId;
		private HashSet<EntityDescription> m_Requirements;

		private List<string> m_InputsId;
		private List<EntityDescription> m_Inputs;
		private Dictionary<EntityDescription, int> m_InputDictionary;

		private Dictionary<string, ActionDescription> m_Actions;

		public EntityDescription(RaceDescription race, XmlNode entityNode)
			: base(entityNode)
		{
			switch (entityNode.Name)
			{
				case "Unit":
					m_Type = EntityType.Unit;
					break;
				case "BuildingWithAddOn":
				case "AddOnAttached":
				case "AddOn":
				case "Building":
					m_Type = EntityType.Building;
					break;
				case "Upgrade":
					m_Type = EntityType.Upgrade;
					break;
			}
			m_Race = race;
			XmlAttribute lifeTime = entityNode.Attributes["lifeTime"];
			XmlAttribute energy = entityNode.Attributes["energy"];
			XmlAttribute icon = entityNode.Attributes["icon"];
			XmlAttribute mineral = entityNode.Attributes["mineral"];
			XmlAttribute vespene = entityNode.Attributes["vespene"];
			XmlAttribute supply = entityNode.Attributes["supply"];
			XmlAttribute time = entityNode.Attributes["time"];
			XmlAttribute inherits = entityNode.Attributes["inherits"];
			XmlAttribute count = entityNode.Attributes["count"];
			XmlNode requirements = entityNode.SelectSingleNode("Requirements");
			XmlNode actions = entityNode.SelectSingleNode("Actions");
			XmlNode input = entityNode.SelectSingleNode("Input");
			if (lifeTime != null)
				m_LifeTime = int.Parse(lifeTime.Value);
			if (energy != null)
				m_HaveEnergy = bool.Parse(energy.Value);
			if (icon != null)
				m_IconId = icon.Value;
			int m = 0, v = 0, t = 0, s = 0;
			if (mineral != null)
				m = int.Parse(mineral.Value);
			if (vespene != null)
				v = int.Parse(vespene.Value);
			if (time != null)
				t = int.Parse(time.Value);
			if (supply != null)
				s = int.Parse(supply.Value);
			m_Cost = new Cost(m, v, t, s);
			if (inherits != null)
				m_InheritsId = inherits.Value;
			if (count != null)
				m_Count = int.Parse(count.Value);
			m_RequirementsId = new HashSet<string>();
			m_Actions = new Dictionary<string, ActionDescription>();
			m_InputsId = new List<string>();
			if (input != null && input.ChildNodes.Count > 0)
				foreach (XmlNode inputNode in input.ChildNodes)
					if (inputNode is XmlElement)
						m_InputsId.Add(inputNode.Attributes["id"].Value);
			if (requirements != null && requirements.ChildNodes.Count > 0)
				foreach (XmlNode requirement in requirements.ChildNodes)
					if (requirement is XmlElement)
						m_RequirementsId.Add(requirement.Attributes["id"].Value);
			if (actions != null && actions.ChildNodes.Count > 0)
				foreach (XmlNode action in actions.ChildNodes)
					if (action is XmlElement)
					{
						ActionDescription a = race.NewAction(this, action);
						m_Actions.Add(a.Id, a);
					}
		}

		private HashSet<EntityDescription> m_Producers;
		public HashSet<EntityDescription> Producers
		{
			get
			{
				if (m_Producers == null)
				{
					HashSet<EntityDescription> producers = new HashSet<EntityDescription>();
					foreach(EntityDescription e in Race.Entities)
						foreach(ActionDescription a in e.Actions)
							if (a is BuildActionDescription)
							{
								BuildActionDescription b = a as BuildActionDescription;
								if (b.Build.Id == Id)
									producers.Add(e);
							}
					m_Producers = producers;
				}
				return m_Producers;
			}
		}

		private int? m_MinTime;
		public int MinTime
		{
			get
			{
				if (!m_MinTime.HasValue)
				{
					if (IsStartEntity)
						m_MinTime = 0;
					else
					{
						foreach (EntityDescription entity in Requirements)
						{
							if(!m_MinTime.HasValue || m_MinTime.Value < entity.MinTime)
								m_MinTime = entity.MinTime;
						}
						foreach (EntityDescription entity in Inputs)
						{
							if (!m_MinTime.HasValue || m_MinTime.Value < entity.MinTime)
								m_MinTime = entity.MinTime;
						}
						if (Producers.Count > 0)
						{
							int minProducers = int.MaxValue;
							foreach (EntityDescription entity in Producers)
							{
								// avoid infinite loop
								if (!entity.Producers.Contains(this))
									minProducers = Math.Min(minProducers, entity.MinTime);
							}
							if (minProducers != int.MaxValue && (!m_MinTime.HasValue || m_MinTime.Value < minProducers))
								m_MinTime = minProducers;
						}
						if (!m_MinTime.HasValue)
							m_MinTime = 0;
						m_MinTime += Time;
					}
				}
				return m_MinTime.Value;
			}
		}

		//private EntityDescription m_FirstProducer;
		//public EntityDescription FirstProducer
		//{
		//   get
		//   {
		//      if (m_FirstProducer == null && Producers.Count > 0)
		//      {
		//         int lowestLevel = int.MaxValue;
		//         EntityDescription producer = null;
		//         foreach (EntityDescription p in Producers)
		//         {
		//            int level = p.Level;
		//            if (lowestLevel > level)
		//            {
		//               producer = p;
		//               lowestLevel = level;
		//            }
		//         }
		//         m_FirstProducer = producer;
		//      }
		//      return m_FirstProducer;
		//   }
		//}

		public RaceDescription Race { get { return m_Race; } }
		public HashSet<EntityDescription> Requirements
		{
			get
			{
				if (m_Requirements == null && (m_RequirementsId != null || Vespene > 0))
				{
					HashSet<EntityDescription> reqs = new HashSet<EntityDescription>();
					if(m_RequirementsId != null)
						foreach (string id in m_RequirementsId)
							reqs.Add(m_Race[id]);
					if (Vespene > 0)
						reqs.Add(m_Race.Vespene);
					m_Requirements = reqs;
				}
				return m_Requirements;
			}
		}
		public List<EntityDescription> Inputs
		{
			get
			{
				if (m_Inputs == null && m_InputsId != null)
				{
					List<EntityDescription> input = new List<EntityDescription>();
					foreach (string id in m_InputsId)
						input.Add(m_Race[id]);
					m_Inputs = input;
				}
				return m_Inputs;
			}
		}

		public Dictionary<EntityDescription, int> InputDictionary
		{
			get
			{
				if (m_InputDictionary == null)
				{
					Dictionary<EntityDescription, int> inputs = new Dictionary<EntityDescription, int>();
					foreach (EntityDescription entity in Inputs)
					{
						int count = 0;
						inputs.TryGetValue(entity, out count);
						count++;
						if (count == 1)
							inputs.Add(entity, count);
						else
							inputs[entity] = count;
					}
					m_InputDictionary = inputs;
				}
				return m_InputDictionary;
			}
		}

		public EntityDescription Inherits
		{
			get
			{
				if (m_Inherits == null && m_InheritsId != null)
					m_Inherits = Race[m_InheritsId];
				return m_Inherits;
			}
		}
		public HashSet<EntityDescription> Inheritings
		{
			get
			{
				if (m_Inheritings == null)
				{
					HashSet<EntityDescription> descr = new HashSet<EntityDescription>();
					foreach (EntityDescription entity in Race.Entities)
						if (entity.Is(this) && !entity.Equals(this))
							descr.Add(entity);
					m_Inheritings = descr;
				}
				return m_Inheritings;
			}
		}

		public bool Is(EntityDescription entity) { return Equals(entity) || (Inherits != null && m_Inherits.Is(entity)); }
		public bool IsUnique { get { return m_Type == EntityType.Upgrade; } }
		public virtual string IconId { get { return m_IconId ?? Id; } }
		public bool HaveEnergy { get { return m_HaveEnergy; } }
		public int Mineral { get { return m_Cost.Mineral; } }
		public int Vespene { get { return m_Cost.Vespene; } }
		public int Time { get { return m_Cost.Time; } }
		public Cost Cost { get { return m_Cost; } }
		public int Supply { get { return m_Cost.Supply; } }
		public int Count { get { return m_Count; } }
		public int? LifeTime { get { return m_LifeTime; } }
		public EntityType Type { get { return m_Type; } }

		private int? m_Level;
		public int Level
		{
			get
			{
				if (!m_Level.HasValue)
				{
					if (IsStartEntity)
						m_Level = 0;
					else
					{
						int inputLevel = 0;
						int producerLevel = int.MaxValue;
						int requirementLevel = int.MaxValue;
						if (Inherits != null)
							inputLevel = Inherits.Level;
						foreach (EntityDescription entity in Inputs)
							inputLevel = Math.Max(inputLevel, entity.Level);
						foreach (EntityDescription entity in Requirements)
							requirementLevel = Math.Min(requirementLevel, entity.Level);
						foreach (EntityDescription entity in Producers)
							if(!entity.Producers.Contains(this))
								producerLevel = Math.Min(producerLevel, entity.Level);
						if (requirementLevel == int.MaxValue)
							requirementLevel = 0;
						if (producerLevel == int.MaxValue)
							producerLevel = 0;
						m_Level = Math.Max(requirementLevel, Math.Max(producerLevel, inputLevel)) + 1;
					}
				}
				return m_Level.Value;
			}
		}

		public bool IsWorker { get { return Is(Race.Worker); } }
		public bool IsBase { get { return Is(Race.Base); } }
		public bool IsVespene { get { return Is(Race.Vespene); } }
		public bool IsSupply { get { return Is(Race.Supply); } }

		private bool? m_IsStartEntity;
		public bool IsStartEntity
		{
			get
			{
				if (!m_IsStartEntity.HasValue)
					m_IsStartEntity = Race.IsStartEntity(this);
				return m_IsStartEntity.Value;
			}
		}

		//private bool? m_NeedVespene;
		//public bool NeedVespene
		//{
		//   get
		//   {
		//      if(!m_NeedVespene.HasValue)
		//      {
		//         if (!m_NeedVespene.HasValue)
		//            foreach (EntityDescription input in Inputs)
		//               if (input.NeedVespene)
		//                  m_NeedVespene = true;
		//         if (!m_NeedVespene.HasValue && Producers.Count > 0 && FirstProducer.FirstProducer != this)
		//            if (FirstProducer.NeedVespene)
		//               m_NeedVespene = true;
		//         if (!m_NeedVespene.HasValue)
		//            foreach (EntityDescription requirement in Requirements)
		//               if (requirement.NeedVespene)
		//                  m_NeedVespene = true;
		//         if (!m_NeedVespene.HasValue)
		//            m_NeedVespene = false;
		//      }
		//      return m_NeedVespene.Value;
		//   }
		//}

		public ActionDescription this[string actionId]
		{
			get
			{
				ActionDescription action;
				if (m_Actions.TryGetValue(actionId, out action))
					return action;
				return null;
			}
		}

		public IEnumerable<ActionDescription> Actions { get { return m_Actions.Values; } }

		public virtual void OnExecuteAction(ActionDescription action, GameState state) { }
		public virtual void OnTotalChanged(GameState state, int change)
		{
			if (m_LifeTime.HasValue && change > 0)
			{
				state.Add(new DelayedActionDescription(this, state.Time + m_LifeTime.Value, delegate(GameState innerState)
					{
						state.Add(this, 0, -change, -change);
					}, Id + "Failed"));
			}
			if (Race.Id == "Zerg")
			{
				if ((Id == "Larva" && change < 0) || (Equals(Race.Base) && change > 0 && state[this].Total > change))
				{
					EntityDescription larva = Race["Larva"];
					GameState.EntityState larvaState = state[larva];
					if (larvaState.Total < state.Bases * 3 && larvaState.Building < state.Bases)
					{
						state.Add(larva, 1, 0, 1);
						LarvaActionDescription spawnLarva = new LarvaActionDescription(larva, 15 + state.Time, "LarvaSpawned");
						state.Add(spawnLarva);
					}
				}
			}
		}
	}
}
